﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.DotNet.Cli.Utils;

// Simple console manager
public class Reporter : IReporter
{
    private static SpinLock s_spinlock = new();

    //cannot use auto properties, as those are static
#pragma warning disable IDE0032 // Use auto property
    private static readonly Reporter s_consoleOutReporter = new(AnsiConsole.GetOutput());
    private static readonly Reporter s_consoleErrReporter = new(AnsiConsole.GetError());
#pragma warning restore IDE0032 // Use auto property

    private static IReporter s_errorReporter = s_consoleErrReporter;
    private static IReporter s_outputReporter = s_consoleOutReporter;
    private static IReporter s_verboseReporter = s_consoleOutReporter;

    private readonly AnsiConsole? _console;

    static Reporter()
    {
        Reset();
    }

    private Reporter(AnsiConsole? console)
    {
        _console = console;
    }

    public static Reporter NullReporter { get; } = new(console: null);
    public static Reporter ConsoleOutReporter => s_consoleOutReporter;
    public static Reporter ConsoleErrReporter => s_consoleErrReporter;

    public static IReporter Output { get; private set; } = NullReporter;
    public static IReporter Error { get; private set; } = NullReporter;
    public static IReporter Verbose { get; private set; } = NullReporter;

    /// <summary>
    /// Resets the reporters to write to the current reporters based on <see cref="CommandLoggingContext"/> settings.
    /// </summary>
    public static void Reset()
    {
        UseSpinLock(() =>
        {
            ResetOutput();
            ResetError();
            ResetVerbose();
        });
    }

    /// <summary>
    /// Sets the output reporter to <paramref name="reporter"/>.
    /// The reporter won't be applied if disabled in <see cref="CommandLoggingContext"/>.
    /// </summary>
    /// <param name="reporter"></param>
    public static void SetOutput(IReporter reporter)
    {
        UseSpinLock(() =>
        {
            s_outputReporter = reporter;
            ResetOutput();
        });
    }

    /// <summary>
    /// Sets the error reporter to <paramref name="reporter"/>.
    /// The reporter won't be applied if disabled in <see cref="CommandLoggingContext"/>.
    /// </summary>
    public static void SetError(IReporter reporter)
    {
        UseSpinLock(() =>
        {
            s_errorReporter = reporter;
            ResetError();
        });
    }

    /// <summary>
    /// Sets the verbose reporter to <paramref name="reporter"/>.
    /// The reporter won't be applied if disabled in <see cref="CommandLoggingContext"/>.
    /// </summary>
    public static void SetVerbose(IReporter reporter)
    {
        UseSpinLock(() =>
        {
            s_verboseReporter = reporter;
            ResetVerbose();
        });
    }

    private static void ResetOutput()
    {
        Output = CommandLoggingContext.OutputEnabled ? s_outputReporter : NullReporter;
    }

    private static void ResetError()
    {
        Error = CommandLoggingContext.ErrorEnabled ? s_errorReporter : NullReporter;
    }

    private static void ResetVerbose()
    {
        Verbose = CommandLoggingContext.IsVerbose ? s_verboseReporter : NullReporter;
    }

    public void WriteLine(string message)
    {
        UseSpinLock(() =>
        {
            if (CommandLoggingContext.ShouldPassAnsiCodesThrough)
            {
                _console?.Writer?.WriteLine(message);
            }
            else
            {
                _console?.WriteLine(message);
            }
        });
    }

    public void WriteLine()
    {
        UseSpinLock(() => _console?.Writer?.WriteLine());
    }

    public void Write(string message)
    {
        UseSpinLock(() =>
        {
            if (CommandLoggingContext.ShouldPassAnsiCodesThrough)
            {
                _console?.Writer?.Write(message);
            }
            else
            {
                _console?.Write(message);
            }
        });
    }

    public void WriteLine(string format, params object?[] args) => WriteLine(string.Format(format, args));


    public static void UseSpinLock(Action action)
    {
        bool lockTaken = false;
        try
        {
            s_spinlock.Enter(ref lockTaken);
            action();
        }
        finally
        {
            if (lockTaken)
            {
                s_spinlock.Exit(false);
            }
        }
    }
}
